package com.healthybear.demo.languagechange

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue

/**
 * Unit tests for LanguageUtils
 * Tests all language switching methods, preference storage, and API compatibility logic
 * Requirements: 1.3, 8.1, 8.2, 9.1, 9.2, 9.3, 9.4
 */
@RunWith(MockitoJUnitRunner::class)
class LanguageUtilsTest {

    @Mock
    private lateinit var mockContext: Context
    
    @Mock
    private lateinit var mockSharedPreferences: SharedPreferences
    
    @Mock
    private lateinit var mockEditor: SharedPreferences.Editor
    
    @Mock
    private lateinit var mockResources: Resources
    
    @Mock
    private lateinit var mockConfiguration: Configuration
    
    @Mock
    private lateinit var mockActivity: Activity
    
    @Mock
    private lateinit var mockPackageManager: PackageManager
    
    @Mock
    private lateinit var mockIntent: Intent
    
    private lateinit var appCompatDelegateMock: MockedStatic<AppCompatDelegate>
    private lateinit var buildVersionMock: MockedStatic<Build.VERSION>
    private lateinit var localeMock: MockedStatic<Locale>
    private lateinit var errorHandlerMock: MockedStatic<ErrorHandler>

    @Before
    fun setUp() {
        // Mock SharedPreferences behavior
        whenever(mockContext.getSharedPreferences("language_preferences", Context.MODE_PRIVATE))
            .thenReturn(mockSharedPreferences)
        whenever(mockSharedPreferences.edit()).thenReturn(mockEditor)
        whenever(mockEditor.putString(any(), any())).thenReturn(mockEditor)
        whenever(mockEditor.putLong(any(), any())).thenReturn(mockEditor)
        whenever(mockEditor.commit()).thenReturn(true)
        
        // Mock Resources and Configuration
        whenever(mockContext.resources).thenReturn(mockResources)
        whenever(mockResources.configuration).thenReturn(mockConfiguration)
        
        // Mock PackageManager
        whenever(mockContext.packageManager).thenReturn(mockPackageManager)
        
        // Setup static mocks
        appCompatDelegateMock = mockStatic(AppCompatDelegate::class.java)
        buildVersionMock = mockStatic(Build.VERSION::class.java)
        localeMock = mockStatic(Locale::class.java)
        errorHandlerMock = mockStatic(ErrorHandler::class.java)
        
        // Mock ErrorHandler validation
        errorHandlerMock.`when`<ErrorHandler.ValidationResult> { 
            ErrorHandler.validateLanguageCode("en") 
        }.thenReturn(ErrorHandler.ValidationResult.Valid)
        errorHandlerMock.`when`<ErrorHandler.ValidationResult> { 
            ErrorHandler.validateLanguageCode("zh") 
        }.thenReturn(ErrorHandler.ValidationResult.Valid)
        errorHandlerMock.`when`<ErrorHandler.ValidationResult> { 
            ErrorHandler.validateLanguageCode("ja") 
        }.thenReturn(ErrorHandler.ValidationResult.Valid)
        errorHandlerMock.`when`<ErrorHandler.ValidationResult> { 
            ErrorHandler.validateLanguageCode("invalid") 
        }.thenReturn(ErrorHandler.ValidationResult.Invalid("Unsupported language code: invalid"))
        errorHandlerMock.`when`<String> { 
            ErrorHandler.handleStorageError(any<Context>(), any<Throwable>()) 
        }.thenReturn("en")
        
        // Clear any existing preferences before each test
        LanguageUtils.clearLanguagePreferences(mockContext)
    }

    @After
    fun tearDown() {
        appCompatDelegateMock.close()
        buildVersionMock.close()
        localeMock.close()
        errorHandlerMock.close()
    }

    // Test Requirement 8.1: Save preference to persistent storage
    @Test
    fun `saveLanguagePreference should save valid language code to SharedPreferences`() {
        // Arrange
        val languageCode = "en"
        
        // Act
        LanguageUtils.saveLanguagePreference(mockContext, languageCode)
        
        // Assert
        verify(mockEditor).putString("language_code", languageCode)
        verify(mockEditor).putLong(eq("timestamp"), any())
        verify(mockEditor).commit()
    }

    @Test
    fun `saveLanguagePreference should not save invalid language code`() {
        // Arrange
        val invalidLanguageCode = "invalid"
        
        // Act
        LanguageUtils.saveLanguagePreference(mockContext, invalidLanguageCode)
        
        // Assert - should not save invalid language
        verify(mockEditor, never()).putString("language_code", invalidLanguageCode)
        verify(mockEditor, never()).commit()
    }

    @Test
    fun `saveLanguagePreference should handle SharedPreferences commit failure gracefully`() {
        // Arrange
        val languageCode = "en"
        whenever(mockEditor.commit()).thenReturn(false)
        
        // Act - should not throw exception
        LanguageUtils.saveLanguagePreference(mockContext, languageCode)
        
        // Assert
        verify(mockEditor).commit()
    }

    // Test Requirement 8.2: Load and apply saved language preference
    @Test
    fun `getLanguagePreference should return saved language when valid`() {
        // Arrange
        val savedLanguage = "zh"
        whenever(mockSharedPreferences.getString("language_code", null)).thenReturn(savedLanguage)
        
        // Act
        val result = LanguageUtils.getLanguagePreference(mockContext)
        
        // Assert
        assertEquals(savedLanguage, result)
    }

    // Test Requirement 8.3: Use device's default language if no preference exists
    @Test
    fun `getLanguagePreference should return device default when no preference exists`() {
        // Arrange
        whenever(mockSharedPreferences.getString("language_code", null)).thenReturn(null)
        val deviceLocale = Locale("ja")
        localeMock.`when`<Locale> { Locale.getDefault() }.thenReturn(deviceLocale)
        
        // Act
        val result = LanguageUtils.getLanguagePreference(mockContext)
        
        // Assert
        assertEquals("ja", result)
    }

    // Test Requirement 8.4: Fallback to English when preference is corrupted
    @Test
    fun `getLanguagePreference should fallback to English when preference is corrupted`() {
        // Arrange
        whenever(mockSharedPreferences.getString("language_code", null)).thenReturn("corrupted")
        val deviceLocale = Locale("unsupported")
        localeMock.`when`<Locale> { Locale.getDefault() }.thenReturn(deviceLocale)
        
        // Act
        val result = LanguageUtils.getLanguagePreference(mockContext)
        
        // Assert
        assertEquals("en", result)
    }

    @Test
    fun `getLanguagePreference should handle SharedPreferences exception and return default`() {
        // Arrange
        whenever(mockSharedPreferences.getString(any(), any())).thenThrow(RuntimeException("Storage error"))
        
        // Act
        val result = LanguageUtils.getLanguagePreference(mockContext)
        
        // Assert
        assertEquals("en", result)
    }

    // Test Requirement 9.1: Prefer AppCompatDelegate.setApplicationLocales() on Android API 33+
    @Test
    fun `isApiLevel33OrAbove should return true for API 33 and above`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(33)
        
        // Act
        val result = LanguageUtils.isApiLevel33OrAbove()
        
        // Assert
        assertTrue(result)
    }

    @Test
    fun `isApiLevel33OrAbove should return false for API below 33`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(32)
        
        // Act
        val result = LanguageUtils.isApiLevel33OrAbove()
        
        // Assert
        assertFalse(result)
    }

    // Test Requirement 9.2: Use Configuration.updateConfiguration() approach on older Android versions
    @Test
    fun `getRecommendedImplementationType should return APP_COMPAT_DELEGATE for API 33+`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(33)
        
        // Act
        val result = LanguageUtils.getRecommendedImplementationType()
        
        // Assert
        assertEquals(LanguageUtils.ImplementationType.APP_COMPAT_DELEGATE, result)
    }

    @Test
    fun `getRecommendedImplementationType should return CONFIGURATION_UPDATE for older API`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(32)
        
        // Act
        val result = LanguageUtils.getRecommendedImplementationType()
        
        // Assert
        assertEquals(LanguageUtils.ImplementationType.CONFIGURATION_UPDATE, result)
    }

    // Test Requirement 9.3: Gracefully fallback to alternative methods when API-specific features are unavailable
    @Test
    fun `supportsAppCompatDelegateLocales should return correct value based on API level`() {
        // Test API 33+
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(33)
        assertTrue(LanguageUtils.supportsAppCompatDelegateLocales())
        
        // Test older API
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(32)
        assertFalse(LanguageUtils.supportsAppCompatDelegateLocales())
    }

    // Test language switching methods
    // Test Requirements: 2.1, 2.2, 2.3, 2.4
    @Test
    fun `setLanguageMethod1 should succeed on API 33+ with valid language`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(33)
        val languageCode = "zh"
        val mockLocaleList = mock(LocaleListCompat::class.java)
        
        mockStatic(LocaleListCompat::class.java).use { localeListMock ->
            localeListMock.`when`<LocaleListCompat> { 
                LocaleListCompat.forLanguageTags(languageCode) 
            }.thenReturn(mockLocaleList)
            
            // Act
            val result = LanguageUtils.setLanguageMethod1(mockContext, languageCode)
            
            // Assert
            assertTrue(result)
            appCompatDelegateMock.verify { AppCompatDelegate.setApplicationLocales(mockLocaleList) }
            verify(mockEditor).putString("language_code", languageCode)
        }
    }

    @Test
    fun `setLanguageMethod1 should fail on API below 33`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(32)
        val languageCode = "zh"
        
        // Act
        val result = LanguageUtils.setLanguageMethod1(mockContext, languageCode)
        
        // Assert
        assertFalse(result)
        appCompatDelegateMock.verifyNoInteractions()
    }

    @Test
    fun `setLanguageMethod1 should fail with invalid language code`() {
        // Arrange
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(33)
        val invalidLanguageCode = "invalid"
        
        // Act
        val result = LanguageUtils.setLanguageMethod1(mockContext, invalidLanguageCode)
        
        // Assert
        assertFalse(result)
        appCompatDelegateMock.verifyNoInteractions()
    }

    // Test Requirements: 3.1, 3.2, 3.3, 3.4
    @Test
    fun `setLanguageMethod2 should succeed with valid language code`() {
        // Arrange
        val languageCode = "ja"
        val mockLocale = Locale(languageCode)
        
        mockStatic(Locale::class.java).use { localeMockStatic ->
            localeMockStatic.`when`<Unit> { Locale.setDefault(any()) }.then { }
            
            // Act
            val result = LanguageUtils.setLanguageMethod2(mockContext, languageCode)
            
            // Assert
            assertTrue(result)
            verify(mockEditor).putString("language_code", languageCode)
        }
    }

    @Test
    fun `setLanguageMethod2 should fail with invalid language code`() {
        // Arrange
        val invalidLanguageCode = "invalid"
        
        // Act
        val result = LanguageUtils.setLanguageMethod2(mockContext, invalidLanguageCode)
        
        // Assert
        assertFalse(result)
        verify(mockEditor, never()).putString("language_code", invalidLanguageCode)
    }

    // Test Requirements: 4.1, 4.2, 4.3, 4.4
    @Test
    fun `openSystemLanguageSettings should succeed when intent is resolvable`() {
        // Arrange
        val mockResolveInfo = mock(ResolveInfo::class.java)
        whenever(mockPackageManager.resolveActivity(any<Intent>(), any<Int>())).thenReturn(mockResolveInfo)
        
        // Act
        val result = LanguageUtils.openSystemLanguageSettings(mockContext)
        
        // Assert
        assertTrue(result)
        verify(mockContext).startActivity(any<Intent>())
    }

    @Test
    fun `openSystemLanguageSettings should use fallback when locale settings unavailable`() {
        // Arrange
        whenever(mockPackageManager.resolveActivity(any<Intent>(), any<Int>()))
            .thenReturn(null) // First call (locale settings) returns null
            .thenReturn(mock(ResolveInfo::class.java)) // Second call (general settings) succeeds
        
        // Act
        val result = LanguageUtils.openSystemLanguageSettings(mockContext)
        
        // Assert
        assertTrue(result)
        verify(mockContext).startActivity(any<Intent>())
    }

    @Test
    fun `openSystemLanguageSettings should fail when no settings available`() {
        // Arrange
        whenever(mockPackageManager.resolveActivity(any<Intent>(), any<Int>())).thenReturn(null)
        
        // Act
        val result = LanguageUtils.openSystemLanguageSettings(mockContext)
        
        // Assert
        assertFalse(result)
        verify(mockContext, never()).startActivity(any<Intent>())
    }

    // Test utility methods
    @Test
    fun `isLanguageSupported should return true for supported languages`() {
        assertTrue(LanguageUtils.isLanguageSupported("en"))
        assertTrue(LanguageUtils.isLanguageSupported("zh"))
        assertTrue(LanguageUtils.isLanguageSupported("ja"))
    }

    @Test
    fun `isLanguageSupported should return false for unsupported languages`() {
        assertFalse(LanguageUtils.isLanguageSupported("fr"))
        assertFalse(LanguageUtils.isLanguageSupported("invalid"))
        assertFalse(LanguageUtils.isLanguageSupported(""))
    }

    @Test
    fun `getCurrentLocale should return correct locale for different API levels`() {
        // Test API N and above
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(24)
        val mockLocaleList = mock(android.os.LocaleList::class.java)
        val expectedLocale = Locale("en")
        whenever(mockConfiguration.locales).thenReturn(mockLocaleList)
        whenever(mockLocaleList[0]).thenReturn(expectedLocale)
        
        val result = LanguageUtils.getCurrentLocale(mockContext)
        assertEquals(expectedLocale, result)
    }

    @Test
    fun `attachBaseContext should return context with applied locale`() {
        // Arrange
        val languageCode = "zh"
        whenever(mockSharedPreferences.getString("language_code", null)).thenReturn(languageCode)
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(24)
        
        val mockContextWrapper = mock(Context::class.java)
        whenever(mockContext.createConfigurationContext(any<Configuration>())).thenReturn(mockContextWrapper)
        
        // Act
        val result = LanguageUtils.attachBaseContext(mockContext, languageCode)
        
        // Assert
        assertEquals(mockContextWrapper, result)
    }

    @Test
    fun `attachBaseContext should handle unsupported language gracefully`() {
        // Arrange
        val unsupportedLanguage = "unsupported"
        
        // Act
        val result = LanguageUtils.attachBaseContext(mockContext, unsupportedLanguage)
        
        // Assert
        assertEquals(mockContext, result)
    }

    @Test
    fun `restartActivity should restart activity with smooth transition`() {
        // Arrange
        whenever(mockActivity.intent).thenReturn(mockIntent)
        
        // Act
        LanguageUtils.restartActivity(mockActivity)
        
        // Assert
        verify(mockActivity).finish()
        verify(mockActivity).startActivity(mockIntent)
        verify(mockActivity).overridePendingTransition(
            android.R.anim.fade_in, 
            android.R.anim.fade_out
        )
    }

    @Test
    fun `hasSystemLanguageChanged should detect language changes`() {
        // Arrange
        val currentLocale = Locale("ja")
        val savedLanguage = "en"
        
        whenever(mockSharedPreferences.getString("language_code", null)).thenReturn(savedLanguage)
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(24)
        
        val mockLocaleList = mock(android.os.LocaleList::class.java)
        whenever(mockConfiguration.locales).thenReturn(mockLocaleList)
        whenever(mockLocaleList[0]).thenReturn(currentLocale)
        
        // Act
        val result = LanguageUtils.hasSystemLanguageChanged(mockContext)
        
        // Assert
        assertTrue(result)
    }

    @Test
    fun `hasSystemLanguageChanged should return false when language unchanged`() {
        // Arrange
        val currentLocale = Locale("en")
        val savedLanguage = "en"
        
        whenever(mockSharedPreferences.getString("language_code", null)).thenReturn(savedLanguage)
        buildVersionMock.`when`<Int> { Build.VERSION.SDK_INT }.thenReturn(24)
        
        val mockLocaleList = mock(android.os.LocaleList::class.java)
        whenever(mockConfiguration.locales).thenReturn(mockLocaleList)
        whenever(mockLocaleList[0]).thenReturn(currentLocale)
        
        // Act
        val result = LanguageUtils.hasSystemLanguageChanged(mockContext)
        
        // Assert
        assertFalse(result)
    }

    @Test
    fun `clearLanguagePreferences should clear all preferences`() {
        // Act
        LanguageUtils.clearLanguagePreferences(mockContext)
        
        // Assert
        verify(mockEditor).clear()
        verify(mockEditor).apply()
    }

    // Test SupportedLanguage enum
    @Test
    fun `SupportedLanguage fromCode should return correct language`() {
        assertEquals(LanguageUtils.SupportedLanguage.ENGLISH, LanguageUtils.SupportedLanguage.fromCode("en"))
        assertEquals(LanguageUtils.SupportedLanguage.CHINESE, LanguageUtils.SupportedLanguage.fromCode("zh"))
        assertEquals(LanguageUtils.SupportedLanguage.JAPANESE, LanguageUtils.SupportedLanguage.fromCode("ja"))
        assertEquals(null, LanguageUtils.SupportedLanguage.fromCode("invalid"))
    }

    @Test
    fun `SupportedLanguage getAllCodes should return all language codes`() {
        val codes = LanguageUtils.SupportedLanguage.getAllCodes()
        assertEquals(listOf("en", "zh", "ja"), codes)
    }
}